package com.yilizhiyou.algorithm;

import com.yilizhiyou.model.UserPreference;
import com.yilizhiyou.entity.ScenicSpot;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 协同过滤算法实现
 * 基于用户相似性进行推荐
 * 
 * @author 伊犁智游开发团队
 * @since 1.0.0
 */
@Component
public class CollaborativeFiltering {
    
    /**
     * 基于用户的协同过滤推荐
     * 
     * @param targetUser 目标用户偏好
     * @param allUsers 所有用户偏好
     * @param allSpots 所有景点
     * @param topN 返回前N个推荐
     * @return 推荐的景点列表
     */
    public List<ScenicSpot> recommendByUserSimilarity(UserPreference targetUser, 
                                                     List<UserPreference> allUsers, 
                                                     List<ScenicSpot> allSpots, 
                                                     int topN) {
        
        // 1. 计算用户相似度
        Map<String, Double> userSimilarities = calculateUserSimilarities(targetUser, allUsers);
        
        // 2. 找到最相似的用户
        List<Map.Entry<String, Double>> sortedSimilarities = userSimilarities.entrySet()
            .stream()
            .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
            .collect(Collectors.toList());
        
        // 3. 基于相似用户的偏好推荐景点
        Map<String, Double> spotScores = new HashMap<>();
        
        for (Map.Entry<String, Double> entry : sortedSimilarities) {
            String similarUserId = entry.getKey();
            double similarity = entry.getValue();
            
            // 只考虑相似度大于0.3的用户
            if (similarity < 0.3) break;
            
            // 找到相似用户
            UserPreference similarUser = allUsers.stream()
                .filter(u -> u.getUserId().equals(similarUserId))
                .findFirst()
                .orElse(null);
            
            if (similarUser != null) {
                // 基于相似用户的评分和访问记录计算景点分数
                for (Map.Entry<String, Double> rating : similarUser.getRatingHistory().entrySet()) {
                    String spotId = rating.getKey();
                    double ratingScore = rating.getValue();
                    
                    // 跳过目标用户已经访问过的景点
                    if (targetUser.getVisitHistory().containsKey(spotId)) {
                        continue;
                    }
                    
                    // 计算加权分数
                    double weightedScore = ratingScore * similarity;
                    spotScores.put(spotId, spotScores.getOrDefault(spotId, 0.0) + weightedScore);
                }
            }
        }
        
        // 4. 按分数排序并返回前N个景点
        return spotScores.entrySet()
            .stream()
            .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
            .limit(topN)
            .map(entry -> findSpotById(allSpots, entry.getKey()))
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
    }
    
    /**
     * 基于物品的协同过滤推荐
     * 
     * @param targetUser 目标用户偏好
     * @param allUsers 所有用户偏好
     * @param allSpots 所有景点
     * @param topN 返回前N个推荐
     * @return 推荐的景点列表
     */
    public List<ScenicSpot> recommendByItemSimilarity(UserPreference targetUser, 
                                                     List<UserPreference> allUsers, 
                                                     List<ScenicSpot> allSpots, 
                                                     int topN) {
        
        // 1. 构建用户-景点评分矩阵
        Map<String, Map<String, Double>> userItemMatrix = buildUserItemMatrix(allUsers, allSpots);
        
        // 2. 计算景点相似度
        Map<String, Map<String, Double>> itemSimilarities = calculateItemSimilarities(userItemMatrix, allSpots);
        
        // 3. 基于目标用户已访问的景点推荐相似景点
        Map<String, Double> spotScores = new HashMap<>();
        
        for (String visitedSpotId : targetUser.getVisitHistory().keySet()) {
            Double userRating = targetUser.getRatingHistory().get(visitedSpotId);
            if (userRating == null) userRating = 3.0; // 默认评分
            
            Map<String, Double> similarSpots = itemSimilarities.get(visitedSpotId);
            if (similarSpots != null) {
                for (Map.Entry<String, Double> entry : similarSpots.entrySet()) {
                    String similarSpotId = entry.getKey();
                    double similarity = entry.getValue();
                    
                    // 跳过已访问的景点
                    if (targetUser.getVisitHistory().containsKey(similarSpotId)) {
                        continue;
                    }
                    
                    // 计算推荐分数
                    double score = userRating * similarity;
                    spotScores.put(similarSpotId, spotScores.getOrDefault(similarSpotId, 0.0) + score);
                }
            }
        }
        
        // 4. 按分数排序并返回前N个景点
        return spotScores.entrySet()
            .stream()
            .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
            .limit(topN)
            .map(entry -> findSpotById(allSpots, entry.getKey()))
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
    }
    
    /**
     * 计算用户相似度
     */
    private Map<String, Double> calculateUserSimilarities(UserPreference targetUser, List<UserPreference> allUsers) {
        Map<String, Double> similarities = new HashMap<>();
        
        for (UserPreference user : allUsers) {
            if (!user.getUserId().equals(targetUser.getUserId())) {
                double similarity = targetUser.calculateSimilarity(user);
                similarities.put(user.getUserId(), similarity);
            }
        }
        
        return similarities;
    }
    
    /**
     * 构建用户-景点评分矩阵
     */
    private Map<String, Map<String, Double>> buildUserItemMatrix(List<UserPreference> allUsers, List<ScenicSpot> allSpots) {
        Map<String, Map<String, Double>> matrix = new HashMap<>();
        
        for (UserPreference user : allUsers) {
            Map<String, Double> userRatings = new HashMap<>();
            
            // 添加用户评分
            userRatings.putAll(user.getRatingHistory());
            
            // 为未评分的景点添加默认评分
            for (ScenicSpot spot : allSpots) {
                if (!userRatings.containsKey(spot.getId().toString())) {
                    // 基于用户偏好计算默认评分
                    double defaultRating = calculateDefaultRating(user, spot);
                    userRatings.put(spot.getId().toString(), defaultRating);
                }
            }
            
            matrix.put(user.getUserId(), userRatings);
        }
        
        return matrix;
    }
    
    /**
     * 计算景点相似度
     */
    private Map<String, Map<String, Double>> calculateItemSimilarities(Map<String, Map<String, Double>> userItemMatrix, 
                                                                      List<ScenicSpot> allSpots) {
        Map<String, Map<String, Double>> similarities = new HashMap<>();
        
        for (ScenicSpot spot1 : allSpots) {
            Map<String, Double> spotSimilarities = new HashMap<>();
            String spotId1 = spot1.getId().toString();
            
            for (ScenicSpot spot2 : allSpots) {
                String spotId2 = spot2.getId().toString();
                if (!spotId1.equals(spotId2)) {
                    double similarity = calculateCosineSimilarity(userItemMatrix, spotId1, spotId2);
                    spotSimilarities.put(spotId2, similarity);
                }
            }
            
            similarities.put(spotId1, spotSimilarities);
        }
        
        return similarities;
    }
    
    /**
     * 计算余弦相似度
     */
    private double calculateCosineSimilarity(Map<String, Map<String, Double>> userItemMatrix, 
                                           String spotId1, String spotId2) {
        double dotProduct = 0.0;
        double norm1 = 0.0;
        double norm2 = 0.0;
        
        for (Map<String, Double> userRatings : userItemMatrix.values()) {
            Double rating1 = userRatings.get(spotId1);
            Double rating2 = userRatings.get(spotId2);
            
            if (rating1 != null && rating2 != null) {
                dotProduct += rating1 * rating2;
                norm1 += rating1 * rating1;
                norm2 += rating2 * rating2;
            }
        }
        
        if (norm1 == 0.0 || norm2 == 0.0) {
            return 0.0;
        }
        
        return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
    }
    
    /**
     * 基于用户偏好计算默认评分
     */
    private double calculateDefaultRating(UserPreference user, ScenicSpot spot) {
        double rating = 3.0; // 基础评分
        
        // 根据景点类型调整评分
        String spotType = spot.getType();
        switch (spotType) {
            case "自然风光":
                rating += (user.getNatureInterest() - 3.0) * 0.3;
                break;
            case "人文景观":
                rating += (user.getCulturalInterest() - 3.0) * 0.3;
                break;
            default:
                rating += (user.getHistoricalInterest() - 3.0) * 0.2;
                break;
        }
        
        // 根据价格偏好调整评分
        if (spot.getPrice() != null) {
            double priceLevel = spot.getPrice() / 100.0; // 将价格转换为1-5级别
            rating -= Math.abs(priceLevel - user.getPricePreference()) * 0.2;
        }
        
        // 根据评分调整
        if (spot.getRating() != null) {
            rating += (spot.getRating() - 4.0) * 0.5;
        }
        
        return Math.max(1.0, Math.min(5.0, rating));
    }
    
    /**
     * 根据ID查找景点
     */
    private ScenicSpot findSpotById(List<ScenicSpot> allSpots, String spotId) {
        return allSpots.stream()
            .filter(spot -> spot.getId().toString().equals(spotId))
            .findFirst()
            .orElse(null);
    }
}
